/*
 * @(#)ExtDesktopProperty.java
 *
 * Copyright 2002 JIDE Software Inc. All rights reserved.

 * Copyright (c) 1995, 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package com.element.plaf;

import com.element.plaf.vsnet.ConvertListener;

import javax.swing.*;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.FontUIResource;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.WeakReference;

/**
 * Wrapper for multiple values from the desktop. The value is lazily looked up, and can be accessed using the
 * <code>UIManager.ActiveValue</code> method <code>createValue</code>. If the underlying desktop property changes this
 * will force the UIs to update all known Frames. You can invoke <code>invalidate</code> to force the value to be
 * fetched again.
 */

public class ExtWindowsDesktopProperty implements UIDefaults.ActiveValue {
	/**
	 * Indicates if an updateUI call is pending.
	 */
	private static boolean updatePending;

	/**
	 * PropertyChangeListener attached to the Toolkit.
	 */
	private WeakPCL pcl;
	/**
	 * Key used to lookup value from desktop.
	 */
	private final String[] keys;
	/**
	 * Value to return.
	 */
	private Object[] value;
	/**
	 * Fallback value in case we get null from desktop.
	 */
	private final Object[] fallback;

	/**
	 * Convert color to another color
	 */
	private final ConvertListener listener;
	/**
	 * Toolkit.
	 */
	private Toolkit toolkit;


	/**
	 * Sets whether or not an updateUI call is pending.
	 */
	private static synchronized void setUpdatePending(boolean update) {
		updatePending = update;
	}

	/**
	 * Returns true if a UI update is pending.
	 */
	private static synchronized boolean isUpdatePending() {
		return updatePending;
	}

	/**
	 * Updates the UIs of all the known Frames.
	 */
	private static void updateAllUIs() {
		Frame[] appFrames = Frame.getFrames();
		for (Frame frame : appFrames) {
			updateWindowUI(frame);
		}
	}

	/**
	 * Updates the UI of the passed in window and all its children.
	 */
	private static void updateWindowUI(Window window) {
		SwingUtilities.updateComponentTreeUI(window);
		Window[] ownedWins = window.getOwnedWindows();
		for (Window win : ownedWins) {
			updateWindowUI(win);
		}
	}


	/**
	 * Creates a DesktopProperty.
	 *
	 * @param keys     Key used in looking up desktop value.
	 * @param fallback Value used if desktop property is null.
	 * @param toolkit  Toolkit used to fetch property from, can be null in which default will be used.
	 */
	public ExtWindowsDesktopProperty(String[] keys, Object[] fallback, Toolkit toolkit, ConvertListener listener) {
		this.keys = keys;
		this.fallback = fallback;
		this.toolkit = toolkit;
		this.listener = listener;
	}

	/**
	 * UIManager.LazyValue method, returns the value from the desktop or the fallback value if the desktop value is
	 * null.
	 */
	public Object createValue(UIDefaults table) {
		if (value == null) {
			value = configureValue(getValueFromDesktop());
			if (value[0] == null) {
				value = configureValue(getDefaultValue());
			}
		}
		return listener.convert(value);
	}

	/**
	 * Returns the value from the desktop.
	 */
	protected Object[] getValueFromDesktop() {
		if (this.toolkit == null) {
			this.toolkit = Toolkit.getDefaultToolkit();
		}
		pcl = new WeakPCL(this, toolkit);
		Object[] values = new Object[getKeys().length];
		for (int i = 0; i < getKeys().length; i++) {
			values[i] = toolkit.getDesktopProperty(getKeys()[i]);
			toolkit.addPropertyChangeListener(getKeys()[i], pcl);
		}
		return values;
	}

	/**
	 * Returns the value to use if the desktop property is null.
	 */
	protected Object[] getDefaultValue() {
		return fallback;
	}

	/**
	 * Invalides the current value so that the next invocation of <code>createValue</code> will ask for the property
	 * again.
	 */
	public void invalidate() {
		if (pcl != null) {
			for (int i = 0; i < getKeys().length; i++) {
				toolkit.removePropertyChangeListener(getKeys()[i], pcl);
			}
			toolkit = null;
			pcl = null;
			value = null;
		}
	}

	/**
	 * Requests that all components in the GUI hierarchy be updated to reflect dynamic changes in this look&feel.  This
	 * update occurs by uninstalling and re-installing the UI objects. Requests are batched and collapsed into a single
	 * update pass because often many desktop properties will change at once.
	 */
	protected void updateUI() {
		if (!isUpdatePending()) {
			setUpdatePending(true);
			Runnable uiUpdater = () -> {
				updateAllUIs();
				setUpdatePending(false);
			};
			SwingUtilities.invokeLater(uiUpdater);
		}
	}

	/**
	 * Configures the value as appropriate for a defaults property in the UIDefaults table.
	 */
	protected Object[] configureValue(Object[] value) {
		if (value != null) {
			for (int i = 0; i < value.length; i++) {
				value[i] = configureValue(value[i]);
			}
		}
		return value;
	}

	/**
	 * Configures the value as appropriate for a defaults property in the UIDefaults table.
	 */
	protected Object configureValue(Object value) {
		if (value != null) {
			if (value instanceof Color) {
				return new ColorUIResource((Color) value);
			} else if (value instanceof Font) {
				return new FontUIResource((Font) value);
			} else if (value instanceof UIDefaults.ProxyLazyValue) {
				value = ((UIDefaults.ProxyLazyValue) value).createValue(null);
			} else if (value instanceof UIDefaults.ActiveValue) {
				value = ((UIDefaults.ActiveValue) value).createValue(null);
			}
		}
		return value;
	}

	/**
	 * Returns the key used to lookup the desktop properties value.
	 */
	protected String[] getKeys() {
		return keys;
	}


	/**
	 * As there is typically only one Toolkit, the PropertyChangeListener is handled via a WeakReference so as not to
	 * pin down the DesktopProperty.
	 */
	private static class WeakPCL extends WeakReference implements PropertyChangeListener {
		private final Toolkit kit;

		WeakPCL(Object target, Toolkit kit) {
			super(target);
			this.kit = kit;
		}

		public void propertyChange(PropertyChangeEvent pce) {
			ExtWindowsDesktopProperty property = (ExtWindowsDesktopProperty) get();

			if (property == null) {
				// The property was GC'ed, we're no longer interested in
				// PropertyChanges, remove the listener.
				kit.removePropertyChangeListener(pce.getPropertyName(), this);
			} else {
				property.invalidate();
				property.updateUI();
			}
		}
	}
}
